package com.yunyao.framework.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.util.DateUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;
import com.yunyao.common.constant.enums.AuditStatusEnums;
import com.yunyao.common.constant.enums.DateFormatPattern;
import com.yunyao.common.core.BaseContext;
import com.yunyao.common.dto.audit.AuditFlowTypeEnums;
import com.yunyao.common.dto.audit.AuditResultEnums;
import com.yunyao.common.dto.audit.AuditTaskDto;
import com.yunyao.common.dto.dict.DataDictionary;
import com.yunyao.common.exception.BusinessException;
import com.yunyao.common.utils.DictCacheUtils;
import com.yunyao.common.web.enums.ResultMsgEnum;
import com.yunyao.common.web.util.PageResult;
import com.yunyao.common.web.util.StringUtils;
import com.yunyao.common.web.util.YResult;
import com.yunyao.dao.model.ActHiActinst;
import com.yunyao.dao.model.ActHiProcinst;
import com.yunyao.dao.model.ActHiVarinst;
import com.yunyao.dao.model.EstProcessStartRecord;
import com.yunyao.dao.model.FTempField;
import com.yunyao.dao.model.User;
import com.yunyao.dao.service.act.IActHiActinstService;
import com.yunyao.dao.service.act.IActHiProcinstService;
import com.yunyao.dao.service.act.IActHiVarinstService;
import com.yunyao.dao.service.system.IEstProcessStartRecordService;
import com.yunyao.dao.service.system.IFTempFieldService;
import com.yunyao.dao.service.system.IUserService;
import com.yunyao.framework.dto.audit.AuditHistoryTaskDto;
import com.yunyao.framework.service.AuditService;
import com.yunyao.framework.service.system.DataDictionaryService;
import com.yunyao.framework.vo.audit.AuditHistoryTaskVO;
import com.yunyao.framework.vo.audit.AuditTaskVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author xingjishuai
 * @date 2024-08-16
 */
@Slf4j
@Service
public class AuditServiceImpl implements AuditService {
    @Autowired
    private IUserService iUserService;
    @Autowired
    private IActHiVarinstService iActHiVarinstService;
    @Autowired
    private IActHiActinstService iActHiActinstService;
    @Autowired
    private IActHiProcinstService iActHiProcinstService;
    @Autowired
    private IFTempFieldService ifTempFieldService;
    @Autowired
    private IEstProcessStartRecordService iEstProcessStartRecordService;
    @Autowired
    private DataDictionaryService dataDictionaryService;

    /**
     * 获取历史任务列表
     *
     * @param taskListDto 历史任务列表查询参数封装
     * @return 返回查询结果，包括历史任务的详细信息
     */
    @Override
    public YResult<List<AuditHistoryTaskVO>> getHistoryTaskList(AuditHistoryTaskDto taskListDto) {
        // 提取流程实例ID
        String processInstanceId = taskListDto.getProcessInstanceId();
        // 根据流程实例ID获取历史活动实例列表
        List<ActHiActinst> actHiActInstList = getActInstList(processInstanceId);
        // 从流程实例列表中提取并筛选出不重复的用户ID列表
        List<String> userIdList =
                actHiActInstList.stream().map(ActHiActinst::getAssignee).filter(StringUtils::isNotBlank).distinct().toList();
        // 获取历史活动实例中涉及的用户信息
        Map<String, User> userMap = getUserMap(userIdList);
        // 获取流程中使用的临时字段映射
        Map<String, FTempField> tempFieldMap = getTempFieldMap(processInstanceId);
        // 初始化结果列表
        List<AuditHistoryTaskVO> voList = Lists.newArrayList();
        // 遍历历史活动实例，转换为前端展示的VO
        for (ActHiActinst actHiActinst : actHiActInstList) {
            convertVo(actHiActinst, userMap, tempFieldMap, voList);
        }
        // 返回成功结果
        return YResult.success(voList);
    }

    /**
     * 根据用户ID和是否为待办任务的条件，查询任务列表。
     * 此方法首先根据用户ID和任务状态（是否为待办）查询相应的任务实例，
     * 然后将查询结果转换为指定的VO列表，最后返回包含这些列表的分页结果。
     *
     * @param auditTaskDto 包含查询条件（如分页信息和是否为待办任务）的DTO对象
     * @return 包含任务列表及分页信息的YResult对象，成功时数据部分包含PageResult对象，
     *         包含任务列表（voList）、当前页码、页面大小、总页数和总记录数。
     */
    @Override
    public YResult<PageResult<AuditTaskVO>> getTaskList(AuditTaskDto auditTaskDto) {
        // 获取当前用户ID，用于查询该用户的任务
        String userId = BaseContext.getUserId();
        // 获取是否为待办任务的标志
        Boolean isBacklog = auditTaskDto.getIsBacklog();
        // 构建查询条件，包括分配给当前用户的任务和任务类型为“用户任务”
        LambdaQueryWrapper<ActHiActinst> wrapper =
                new LambdaQueryWrapper<ActHiActinst>().eq(ActHiActinst::getAssignee, userId)
                        .eq(ActHiActinst::getActType, "userTask");

        // 根据是否为待办任务，设置查询结束时间的条件
        if (isBacklog) {
            wrapper.isNull(ActHiActinst::getEndTime);
        } else {
            wrapper.isNotNull(ActHiActinst::getEndTime);
        }

        // 按照开始时间降序排序任务
        wrapper.orderByDesc(ActHiActinst::getStartTime);

        // 创建分页对象，设置当前页码和每页显示的记录数
        IPage<ActHiActinst> actInstPage = new Page<>(auditTaskDto.getPageIndex(),
                auditTaskDto.getPageSize());
        // 执行分页查询
        actInstPage = iActHiActinstService.page(actInstPage, wrapper);
        // 获取查询结果列表
        List<ActHiActinst> actInstList = actInstPage.getRecords();

        // 如果查询结果为空，返回一个空的分页结果
        if (CollectionUtils.isEmpty(actInstList)) {
            return YResult.success(new PageResult<>());
        }

        // 将查询结果转换为VO列表
        List<AuditTaskVO> voList = convertVoTaskList(actInstList);

        // 返回包含分页信息和任务列表的分页结果
        return YResult.success(new PageResult<>(voList, actInstPage.getCurrent(),
                actInstPage.getSize(), actInstPage.getPages(), actInstPage.getTotal()));
    }

    /**
     * 将流程实例信息列表转换为审核任务视图对象列表
     *
     * @param actInstList 流程实例信息列表
     * @return 审核任务视图对象列表
     */
    private List<AuditTaskVO> convertVoTaskList(List<ActHiActinst> actInstList) {
        // 从流程实例信息列表中提取并过滤出唯一的流程实例ID列表
        List<String> processInstanceIdList =
                actInstList.stream().map(ActHiActinst::getProcInstId).filter(StringUtils::isNotBlank).distinct().toList();
        if (CollectionUtils.isEmpty(processInstanceIdList)) {
            // 如果流程实例ID列表为空，则返回空列表
            return Lists.newArrayList();
        }
        // 根据流程实例ID列表查询启动流程的记录
        List<EstProcessStartRecord> processStartRecords =
                iEstProcessStartRecordService.listByProcessInstanceIdList(processInstanceIdList);
        if (CollectionUtils.isEmpty(processStartRecords)) {
            // 如果启动流程的记录为空，则返回空列表
            return Lists.newArrayList();
        }
        // 从启动流程的记录中提取并过滤出唯一不为空的创建者ID列表
        List<String> userIdList =
                processStartRecords.stream().map(EstProcessStartRecord::getCreatedBy).filter(StringUtils::isNotBlank).distinct().toList();
        // 根据用户ID列表获取用户信息映射
        Map<String, User> userMap = getUserMap(userIdList);
        // 将启动流程的记录转换为以流程实例ID为键的映射
        Map<String, EstProcessStartRecord> recordMap =
                processStartRecords.stream().collect(Collectors.toMap(EstProcessStartRecord::getProcessInstanceId, Function.identity(), (x, y) -> x));
        // 获取流程变量实例信息映射
        Map<String, ActHiVarinst> hiVarinstMap = getVarinstMap(processInstanceIdList);
        // 创建审核任务视图对象列表
        List<AuditTaskVO> list = Lists.newArrayList();
        // 遍历流程实例信息，转换为审核任务视图对象并添加到列表中
        for (ActHiActinst actHiActinst : actInstList) {
            AuditTaskVO auditTaskVO = convertVoTask(actHiActinst, recordMap, userMap, hiVarinstMap);
            if (Objects.isNull(auditTaskVO)) {
                // 如果转换结果为空，则跳过当前循环
                continue;
            }
            // 将转换后的审核任务视图对象添加到列表中
            list.add(auditTaskVO);
        }
        // 返回审核任务视图对象列表
        return list;
    }

    private Map<String, ActHiVarinst> getVarinstMap(List<String> processInstanceIdList) {
        List<ActHiVarinst> varInstList =
                iActHiVarinstService.listByProcessInstanceIdList(processInstanceIdList);
        Map<String, ActHiVarinst> hiVarinstMap =
                varInstList.stream().collect(Collectors.toMap(x -> x.getProcInstId() + "_" + x.getName(), Function.identity(), (x, y) -> x));
        return hiVarinstMap;
    }

    /**
     * 将流程历史活动实例转换为审核任务视图对象
     *
     * @param actHiActinst    历史活动实例
     * @param recordMap       流程启动记录映射
     * @param userMap         用户信息映射
     * @param hiVarinstMap    历史变量实例映射
     * @return AuditTaskVO    审核任务视图对象，如果无法转换则返回null
     */
    private AuditTaskVO convertVoTask(ActHiActinst actHiActinst,
                                      Map<String, EstProcessStartRecord> recordMap, Map<String, User> userMap, Map<String, ActHiVarinst> hiVarinstMap) {
        // 获取流程实例ID
        String procInstId = actHiActinst.getProcInstId();
        // 如果流程实例ID为空，则返回null
        if (StringUtils.isBlank(procInstId)) {
            return null;
        }
        // 如果流程启动记录中不包含该流程实例ID，则返回null
        if (!recordMap.containsKey(procInstId)) {
            return null;
        }
        // 获取活动ID
        String actId = actHiActinst.getActId();
        // 如果活动ID为空，则返回null
        if (StringUtils.isBlank(actId)) {
            return null;
        }

        // 从记录映射中获取流程启动记录
        EstProcessStartRecord processStartRecord = recordMap.get(procInstId);
        // 获取创建用户的ID
        String createdUserId = processStartRecord.getCreatedBy();
        // 创建审核任务视图对象
        AuditTaskVO auditTaskVO = new AuditTaskVO();
        // 设置创建用户ID
        auditTaskVO.setCreatedUserId(createdUserId);
        // 设置流程实例ID
        auditTaskVO.setProcessInstanceId(procInstId);
        // 设置业务ID
        auditTaskVO.setBizId(processStartRecord.getBizId());
        // 设置业务名称
        auditTaskVO.setBizName(processStartRecord.getBizName());
        // 设置业务类型
        auditTaskVO.setBizType(processStartRecord.getModuleName());
        // 设置业务类型名称
        auditTaskVO.setBizTypeName(AuditFlowTypeEnums.typeOfName(processStartRecord.getModuleName()));
        // 检查用户映射中是否包含创建用户ID
        boolean containsKey = userMap.containsKey(createdUserId);
        // 如果包含，则设置创建用户名称
        if (containsKey) {
            User user = userMap.get(createdUserId);
            auditTaskVO.setCreatedUserName(user.getRealName());
        }
        // 设置创建时间
        auditTaskVO.setCreatedAt(DateUtils.format(processStartRecord.getCreatedAt(),
                DateFormatPattern.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getPattern()));
        // 设置任务到达时间
        auditTaskVO.setTaskAt(DateUtils.format(actHiActinst.getStartTime(),DateFormatPattern.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getPattern()));
        // 构建变量实例的键
        String key = procInstId + "_" + actId + "_btnValue";
        // 默认设置审核状态为已提交
        auditTaskVO.setAuditStatus(AuditStatusEnums.SUBMITTED.getType());
        // 如果历史变量实例映射中包含键，则根据文本值设置审核状态
        if (hiVarinstMap.containsKey(key)) {
            ActHiVarinst varInst = hiVarinstMap.get(key);
            String text = varInst.getText();
            if (StringUtils.isNotBlank(text)) {
                auditTaskVO.setAuditStatus(StringUtils.equals(text,
                        AuditResultEnums.AGREE.getKey()) ?
                        AuditStatusEnums.AUDIT_SUCCESS.getType() :
                        AuditStatusEnums.AUDIT_REJECT.getType());
            }
        }

        //从字典中获取流程对应审核链接和详情链接
        //缓存中获取
        DataDictionary dataDictionary = DictCacheUtils.getDictData(auditTaskVO.getBizType(), "auditBizTypeUrl");
        if (dataDictionary != null) {
            auditTaskVO.setAuditBtnUrl(dataDictionary.getValue());
            auditTaskVO.setAuditDetailUrl(dataDictionary.getRemark()+"?id="+auditTaskVO.getBizId());
        }else {
            //数据库中获取
            List<DataDictionary> dictionaryList = dataDictionaryService.getDictionaryList("auditBizTypeUrl");
            if (dictionaryList != null && dictionaryList.size() > 0) {
                for (DataDictionary dictionary : dictionaryList) {
                    if(dictionary.getKey().equals(auditTaskVO.getBizType())){
                        auditTaskVO.setAuditBtnUrl(dictionary.getValue());
                        auditTaskVO.setAuditDetailUrl(dictionary.getRemark()+"?id="+auditTaskVO.getBizId());
                    }
                }
            }
        }


        // 返回审核任务视图对象
        return auditTaskVO;
    }



    /**
     * 将流程历史活动实例转换为审核历史任务视图对象
     *
     * @param actHiActinst 流程历史活动实例
     * @param userMap      用户ID到用户信息的映射
     * @param tempFieldMap 临时字段键值对，存储流程中的临时数据
     * @param voList       转换后的审核历史任务视图对象列表
     */
    private void convertVo(ActHiActinst actHiActinst, Map<String, User> userMap, Map<String,
            FTempField> tempFieldMap, List<AuditHistoryTaskVO> voList) {
        // 创建一个新的审核历史任务视图对象
        AuditHistoryTaskVO historyTaskVO = new AuditHistoryTaskVO();
        // 获取流程实例ID
        String procInstId = actHiActinst.getProcInstId();
        // 获取活动类型
        String actType = actHiActinst.getActType();
        // 获取活动ID
        String actId = actHiActinst.getActId();
        // 如果活动类型是序列流（流程连线），则跳过转换
        if (StringUtils.equals("sequenceFlow", actType)) {
            return;
        }
        // 如果活动类型是结束事件，则设置审核结果为结束
        if (StringUtils.equals("endEvent", actType)) {
            historyTaskVO.setAuditResult(AuditResultEnums.END.getKey());
            historyTaskVO.setAuditResultMsg(AuditResultEnums.END.getMsg());
        }

        // 设置活动ID到审核历史任务视图对象
        historyTaskVO.setActId(actId);
        // 设置活动名称到审核历史任务视图对象
        historyTaskVO.setActName(actHiActinst.getActName());
        historyTaskVO.setActType(actType);
        // 设置审核日期到审核历史任务视图对象
        Date endTime = actHiActinst.getEndTime();
        if (endTime != null) {
            historyTaskVO.setAuditDate(DateUtils.format(endTime, DateFormatPattern.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getPattern()));
        }
        // 获取办理人ID
        String assignee = actHiActinst.getAssignee();
        // 如果办理人ID不为空
        if (StringUtils.isNotBlank(assignee)) {
            // 设置审核用户ID到审核历史任务视图对象
            historyTaskVO.setAuditUserId(assignee);
            // 检查用户映射中是否包含办理人ID
            boolean userContainsKey = userMap.containsKey(assignee);
            // 如果包含，则获取用户信息并设置审核人名称
            if (userContainsKey) {
                User user = userMap.get(assignee);
                historyTaskVO.setAuditUserName(user.getRealName());
            }
        }
        // 构建临时字段的键
        String key = procInstId + "@MACRO@" + actId;
        // 如果临时字段映射中包含键，则获取临时值并解析
        if (tempFieldMap.containsKey(key)) {
            FTempField tempField = tempFieldMap.get(key);
            String tempValue = tempField.getTempValue();
            try {
                JSONObject parseObject = JSON.parseObject(tempValue);
                // 如果解析成功，则设置审核结果、审核结果消息和审核信息
                if (parseObject != null) {
                    String btnValue = parseObject.getString("btnValue");
                    historyTaskVO.setAuditResult(btnValue);
                    historyTaskVO.setAuditResultMsg(AuditResultEnums.getMsgByKey(btnValue));
                    historyTaskVO.setAuditMessage(parseObject.getString("message"));
                    historyTaskVO.setAttachList(parseObject.getString("attachList"));
                }
            } catch (Exception e) {
                log.error("解析临时值失败", e);
            }
        }
        if (StringUtils.isBlank(historyTaskVO.getAttachList())){
            historyTaskVO.setAttachList("[]");
        }
        // 将审核历史任务视图对象添加到列表中
        voList.add(historyTaskVO);
    }


    /**
     * 根据流程实例ID获取流程节点历史信息列表
     *
     * @param processInstanceId 流程实例ID
     * @return 流程节点历史信息列表
     * @throws BusinessException 如果输入的流程实例ID为空，或者根据流程实例ID未查询到相关记录，
     *                           或者流程启动记录中的业务ID为空，或者未查询到对应的流程记录，将抛出业务异常
     */
    private List<ActHiActinst> getActInstList(String processInstanceId) {
        // 检查流程实例ID是否为空
        if (StringUtils.isBlank(processInstanceId)) {
            throw new BusinessException(ResultMsgEnum.PARAM_ERROR);
        }
        // 根据流程实例ID查询流程实例的历史信息
        ActHiProcinst actHiProcinst =
                iActHiProcinstService.getByProcessInstanceId(processInstanceId);
        // 如果未查询到流程实例的历史信息，抛出异常
        if (actHiProcinst == null) {
            throw new BusinessException("未查询到记录");
        }
        // 根据流程实例ID查询流程的启动记录
        EstProcessStartRecord processStartRecord =
                iEstProcessStartRecordService.getByProcessInstanceId(processInstanceId);
        // 如果未查询到流程的启动记录，抛出异常
        if (processStartRecord == null) {
            throw new BusinessException("未查询到开始记录");
        }
        // 获取流程启动记录中的业务ID
        String bizId = processStartRecord.getBizId();
        // 如果业务ID为空，抛出异常
        if (StringUtils.isBlank(bizId)) {
            throw new BusinessException("未查询到业务Id");
        }
        // 根据业务ID查询所有的流程启动记录
        List<EstProcessStartRecord> startRecordList =
                iEstProcessStartRecordService.listByBizId(bizId);
        // 如果未查询到任何流程启动记录，抛出异常
        if (CollectionUtils.isEmpty(startRecordList)) {
            throw new BusinessException("未查询到查询流程记录");
        }
        // 从流程启动记录中提取并筛选出非空的流程实例ID列表
        List<String> processInstanceIdList =
                startRecordList.stream()
                        .map(EstProcessStartRecord::getProcessInstanceId)
                        .filter(StringUtils::isNotBlank)
                        .distinct()
                        .toList();
        // 如果流程实例ID列表为空，抛出异常
        if (CollectionUtils.isEmpty(processInstanceIdList)) {
            throw new BusinessException("未查询到查询流程Id");
        }
        // 根据流程实例ID列表查询对应的流程节点历史信息列表
        List<ActHiActinst> actHiActInstList =
                iActHiActinstService.listByProcessInstanceIdList(processInstanceIdList);
        // 如果未查询到任何流程节点历史信息，抛出异常
        if (CollectionUtils.isEmpty(actHiActInstList)) {
            throw new BusinessException("未查询到查询流程节点");
        }
        // 返回查询到的流程节点历史信息列表
        return actHiActInstList;
    }

    /**
     * 根据流程实例ID获取临时字段映射
     * 该方法解释了为什么需要根据流程实例ID获取临时字段的映射，即为了高效地通过流程实例ID访问临时字段
     *
     * @param processInstanceId 流程实例ID，用于查询相关的临时字段
     * @return 返回一个Map，其中包含临时字段的键值对，键是临时字段的唯一键
     */
    private Map<String, FTempField> getTempFieldMap(String processInstanceId) {
        // 查询与给定流程实例ID相关的所有临时字段
        List<FTempField> tempFieldList =
                ifTempFieldService.listByLikeProcessInstanceId(processInstanceId);
        // 如果临时字段列表为空，则直接返回一个空的Map
        if (CollectionUtils.isEmpty(tempFieldList)) {
            return Maps.newHashMap();
        }
        // 使用流将临时字段列表转换为Map，以实现通过临时键快速访问临时字段
        // 当键冲突时，选择第一个值，冲突的情况应该很少，所以这里直接覆盖
        return tempFieldList.stream().collect(Collectors.toMap(FTempField::getTempKey,
                Function.identity(), (v1, v2) -> v1));
    }


    /**
     * 根据流程实例获取用户信息映射
     *
     * @param userIdList  用户ID
     * @return 用户ID与用户信息的映射关系
     */
    private Map<String, User> getUserMap(List<String> userIdList) {


        // 根据用户ID列表查询对应的用户信息
        List<User> userList = iUserService.listByUserId(userIdList);

        // 如果用户信息列表为空，则返回空的映射
        if (CollectionUtils.isEmpty(userList)) {
            return Maps.newHashMap();
        }

        // 将用户信息列表转换为以用户ID为键的映射
        return userList.stream().collect(Collectors.toMap(User::getId,
                Function.identity(), (v1, v2) -> v1));
    }
}
